/*
 * HttpFacade.java
 *
 * Copyright 2007, 2008 Tiberiumlabs
 *
 * This file is part of Tiberiumlabs Lailaps.
 *
 * Tiberiumlabs Lailaps is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Tiberiumlabs Lailaps is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.tiberiumlabs.lailaps.threads.single;

import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.TimeZone;
import java.util.WeakHashMap;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.util.URIUtil;
import org.tiberiumlabs.lailaps.Mediator;
import org.tiberiumlabs.lailaps.settings.Settings;

/**
 * @author <a href="mailto:paranoid.tiberiumlabs@gmail.com">Paranoid</a>
 */
public class HttpFacade implements MethodFacade {

    // <editor-fold defaultstate="collapsed" desc=" i18n and statis strings ">
    private static final String RANGE = "Range";
    private static final String BYTES = "bytes=";
    private static final String LAST_MODIFIED = "Last-Modified";
    private static final String DATE = "Date";

    private static final String ILLEGAL_HTTP_STATE = "error.http.illegalstate";
    private static final String ANSWER_CODE = "http.answercode";
    //private static final String ENCODING = System.getProperty("file.encoding");
    //private static final String LOCATION = "Location";
    /**
     * Date format patterns used to parse HTTP date headers.
     */
    private static final String[] PATTERNS = {"EEE, dd MMM yyyy HH:mm:ss z", "EEEE, dd-MMM-yy HH:mm:ss zzz", "EEE MMM d HH:mm:ss yyyy"};

    private static final ArrayList<String> ENCODINGS = Settings.getNetworkSettings().getCharsetList();

    // </editor-fold>
    private static final HttpClient httpClient = Mediator.getMediator().getHttpClient();
    private static final Header[] headers = Mediator.getMediator().getHttpRequestHeaders();

    private ResourceBundle bundle = null;
    private String url = null;
    //private String encodedUrl = null;
    private long offset = 0;

    private GetMethod get = null;

    @Override
    public void setBundle(ResourceBundle bundle) {
        this.bundle = bundle;
    }

    @Override
    public void setUrl(String url) {
        this.url = url;
    }

    @Override
    public void setOffset(long offset) {
        this.offset = offset;
    }

    @Override
    public void execute() throws IOException {

        int res = 666; // TODO just for make it greater than any HTTP error code
        for (String encoding : ENCODINGS) {

            get = new GetMethod(urlEncode(url, encoding));

            // TODO search for better way to set headers, may be for full HttpClient
            for (Header header : headers) {
                get.setRequestHeader(header);
            }

            if (offset > 0) {
                get.setRequestHeader(RANGE, BYTES + offset + '-');
            }

            // TODO wait for timeout here and try next encoding for url.
            res = httpClient.executeMethod(get);

            log.info(url + ' ' + bundle.getString(ANSWER_CODE) + ' ' + res + ' ' + get.getStatusLine().getReasonPhrase());

            // <editor-fold defaultstate="collapsed" desc=" hand-checking for redirects. not used for now. ">
            // checking if it is just redirect:
            // by default followRedirects in HttpClient is enabled, so think we will not need this code...
            /*
            if (res >= 300 && res < 400) {
            log.warn("Url " + url + " answered with redirect!");
            Header header = get.getResponseHeader(LOCATION);
            if (header != null) {
            String location = header.getValue();
            if (location != null && !location.isEmpty()) {
            encodedUrl = location;
            execute();
            return;
            }
            }
            get.abort();
            get.releaseConnection();
            throw new IllegalStateException(bundle.getString(ILLEGAL_HTTP_STATE) + res + ',' + ' ' + url);
            }
             */
            // </editor-fold>
            
            if (res < 400) {
                return;
            }

            // TODO check if we need to release connection.
            // connection was established but answer >= 400.
            try {
                get.releaseConnection();
            } catch (Exception ignore) {
            }
        }

        if (res >= 400) {
            throw new IllegalStateException(bundle.getString(ILLEGAL_HTTP_STATE) + ' ' + res + ',' + ' ' + url);
        }
    }

    @Override
    public long getLength() {
        return get.getResponseContentLength();
    }

    @Override
    public long getDate() {
        if (get != null) {
            Header dateHeader = get.getResponseHeader(LAST_MODIFIED);
            if (dateHeader == null) {
                dateHeader = get.getResponseHeader(DATE);
            }
            if (dateHeader != null) {
                String lastModified = dateHeader.getValue();
                if (lastModified != null && !lastModified.isEmpty()) {
                    for (int i = 0; i < PATTERNS.length; i++) {
                        try {
                            return getDateFormatter(PATTERNS[i]).parse(lastModified).getTime();
                        } catch (ParseException ignore) {
                            // we'll try next pattern
                        }
                    }
                }
            }
        }
        return 0;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return get.getResponseBodyAsStream();
    }

    @Override
    public void close() throws IOException {
        get.releaseConnection();
    }

    @Override
    public void abort() throws IOException {
        if (get != null) {
            get.abort();
            get.releaseConnection();
        }
    }

    private static final WeakHashMap<String, SimpleDateFormat> formattersMap = new WeakHashMap<String, SimpleDateFormat>();

    private static SimpleDateFormat getDateFormatter(String pattern) {
        SimpleDateFormat format = formattersMap.get(pattern);
        if (format == null) {
            format = new SimpleDateFormat(pattern, Locale.US);
            format.setTimeZone(TimeZone.getTimeZone("GTM"));
            formattersMap.put(pattern, format);
        }
        return format;
    }

    // <editor-fold defaultstate="collapsed" desc=" URL encoders ">
    private String urlEncode(String url, String encoding) {
        try {
            return URIUtil.encodePathQuery(url, encoding);
        } catch (URIException ex) {
            try {
                return URIUtil.encodePathQuery(url);
            } catch (URIException exx) {
                log.warn("Exception while encoding url: " + url, exx);
            }
        }
        return url;
    }
    // </editor-fold>
}
